package com.bucket.cloud.common.core.aspect;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.bucket.cloud.common.core.annotation.CustomLog;
import com.bucket.cloud.common.core.support.SendLog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamSource;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.util.StopWatch;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.File;
import java.lang.reflect.Method;

/**
 * code is far away from bug with the animal protecting
 * ┏┓　　　┏┓
 * ┏┛┻━━━┛┻┓
 * ┃　　　　　　　┃
 * ┃　　　━　　　┃
 * ┃　┳┛　┗┳　┃
 * ┃　　　　　　　┃
 * ┃　　　┻　　　┃
 * ┃　　　　　　　┃
 * ┗━┓　　　┏━┛
 * 　　┃　　　┃神兽保佑
 * 　　┃　　　┃代码无BUG！
 * 　　┃　　　┗━━━┓
 * 　　┃　　　　　　　┣┓
 * 　　┃　　　　　　　┏┛
 * 　　┗┓┓┏━┳┓┏┛
 * 　　　┃┫┫　┃┫┫
 * 　　　┗┻┛　┗┻┛
 *
 * @Description : 通用的日志注解切面
 * ---------------------------------
 * @Author : cyq
 * @Date : Create in 2018/11/20 09:51
 */
@Component
@Aspect
@Slf4j
public class CustomLogAspect {


    // 执行最大时间 超过该时间则警告
    private static final int DEFAULT_TIME_LIMIT = 3000;
    private static final String MSG = "--请求--\n --方法：{}\n --描述：{}\n --位置：{}\n --参数：{}\n --返回：{}\n --耗时：{} ms";

    @Autowired
    private SendLog sendLog;

    // 切点`
    @Pointcut("@annotation(com.bucket.cloud.common.core.annotation.CustomLog)")
    public void executePointCut() {

    }


    // around 切面强化
    @Around("executePointCut()")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        if (log.isDebugEnabled() || log.isWarnEnabled()) {
            StopWatch clock = new StopWatch();
            clock.start();
            Object retrunobj = null;
            try {
                // 注意和finally中的执行顺序 finally是在return中的计算结束返回前执行
                return retrunobj = joinPoint.proceed(args);
            } catch (Exception e) {
                throw e;
            } finally {
                clock.stop();
                long totalTime = clock.getTotalTimeMillis();
                // 打印日志
                handleLog(joinPoint, args, retrunobj, totalTime);
            }
        } else {
            return joinPoint.proceed(args);
        }


    }



    /**
     * 日志处理
     *
     * @param joinPoint 位置
     * @param args      参数
     * @param retrunobj 响应
     * @param totalTime  耗时ms
     */
    private void handleLog(ProceedingJoinPoint joinPoint, Object[] args, Object retrunobj, long totalTime) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        CustomLog customLog = method.getAnnotation(CustomLog.class);
        printLogMsg(customLog.name(), customLog.description(), customLog.printReturn(), joinPoint, args, retrunobj, totalTime);
        // 发送到MQ，然后由日志服务去统一保存
        // 功能名，功能描述，功能类型，参数，返回，访问耗时
        sendLog.send(customLog.name(), customLog.description(), customLog.type().getType(), joinPoint.getStaticPart().toString()
                , JSONArray.toJSONString(args), getPrintMsg(retrunobj), totalTime);

    }

    /**
     * @param name            操作名称
     * @param description     描述
     * @param printReturn     是否打印响应
     * @param joinPoint       位置
     * @param args            参数
     * @param returnObj       响应
     * @param totalTimeMillis 耗时ms
     */
    protected void printLogMsg(String name, String description, boolean printReturn, JoinPoint joinPoint, Object[] args, Object returnObj, long totalTimeMillis) {
        Object[] params = argsDemote(args);
        if(printReturn){
            if (totalTimeMillis < DEFAULT_TIME_LIMIT)
                log.info(MSG, new Object[]{name, description, joinPoint.getStaticPart(), params, getPrintMsg(returnObj), totalTimeMillis});
            else
                log.warn(MSG, new Object[]{name, description, joinPoint.getStaticPart(), params, getPrintMsg(returnObj), totalTimeMillis});
        }
    }


    private String getPrintMsg(Object returnObj) {
        return (returnObj != null) ? JSONObject.toJSONString(returnObj) : "null";
    }

    private String getPrintMsg(boolean printReturn, Object returnObj) {
        return printReturn ? ((returnObj != null) ? JSONObject.toJSONString(returnObj) : "null") : "[printReturn = false]";
    }

    private Object[] argsDemote(Object[] args) {
        if (args == null || args.length == 0) {
            return new Object[]{};
        }
        Object[] params = new Object[args.length];

        for (int i = 0; i < args.length; i++) {
            Object arg = args[i];
            if (arg instanceof ServletRequest || arg instanceof ServletResponse || arg instanceof ModelMap
                    || arg instanceof Model || arg instanceof InputStreamSource ||
                    arg instanceof File) {
                params[i] = args.toString();
            } else {
                params[i] = JSONArray.toJSONString(args[i]);
            }
        }
        return params;
    }
}

